package org.dromara.common.redis.utils

import cn.hutool.core.convert.Convert
import cn.hutool.core.date.DatePattern
import org.dromara.common.core.utils.SpringUtils
import org.dromara.common.core.utils.StringUtils
import org.redisson.api.RIdGenerator
import org.redisson.api.RedissonClient
import java.time.Duration
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.time.temporal.TemporalAccessor

/**
 * 发号器工具类
 *
 * @author LikeYouDo
 */
object SequenceUtils {
    /**
     * 默认初始值
     */
    const val DEFAULT_INIT_VALUE: Long = 1L

    /**
     * 默认步长
     */
    const val DEFAULT_STEP_VALUE: Long = 1L

    /**
     * 默认过期时间-天
     */
    @JvmField
    val DEFAULT_EXPIRE_TIME_DAY: Duration? = Duration.ofDays(1)

    /**
     * 默认过期时间-分钟
     */
    @JvmField
    val DEFAULT_EXPIRE_TIME_MINUTE: Duration? = Duration.ofMinutes(1)

    /**
     * 默认最小ID容量位数 - 6位数（即至少可以生成的ID为999999个）
     */
    const val DEFAULT_MIN_ID_CAPACITY_BITS: Int = 6

    /**
     * 获取Redisson客户端实例
     */
    private val REDISSON_CLIENT: RedissonClient = SpringUtils.getBean(RedissonClient::class.java)

    /**
     * 获取ID生成器
     *
     * @param key        业务key
     * @param expireTime 过期时间
     * @param initValue  ID初始值
     * @param stepValue  ID步长
     * @return ID生成器
     */
    @JvmStatic
    fun getIdGenerator(key: String?, expireTime: Duration?, initValue: Long, stepValue: Long): RIdGenerator {
        var initValue = initValue
        var stepValue = stepValue
        val idGenerator = REDISSON_CLIENT.getIdGenerator(key)
        // 初始值和步长不能小于等于0
        initValue = if (initValue <= 0) DEFAULT_INIT_VALUE else initValue
        stepValue = if (stepValue <= 0) DEFAULT_STEP_VALUE else stepValue
        // 设置初始值和步长
        idGenerator.tryInit(initValue, stepValue)
        // 设置过期时间
        idGenerator.expire(expireTime)
        return idGenerator
    }

    /**
     * 获取ID生成器
     *
     * @param key        业务key
     * @param expireTime 过期时间
     * @return ID生成器
     */
    @JvmStatic
    fun getIdGenerator(key: String?, expireTime: Duration?): RIdGenerator {
        return getIdGenerator(key, expireTime, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE)
    }

    /**
     * 获取指定业务key的唯一id
     *
     * @param key        业务key
     * @param expireTime 过期时间
     * @param initValue  ID初始值
     * @param stepValue  ID步长
     * @return 唯一id
     */
    @JvmStatic
    fun getNextId(key: String?, expireTime: Duration?, initValue: Long, stepValue: Long): Long {
        return getIdGenerator(key, expireTime, initValue, stepValue).nextId()
    }

    /**
     * 获取指定业务key的唯一id (ID初始值=1,ID步长=1)
     *
     * @param key        业务key
     * @param expireTime 过期时间
     * @return 唯一id
     */
    @JvmStatic
    fun getNextId(key: String?, expireTime: Duration?): Long {
        return getIdGenerator(key, expireTime).nextId()
    }

    /**
     * 获取指定业务key的唯一id字符串
     *
     * @param key        业务key
     * @param expireTime 过期时间
     * @param initValue  ID初始值
     * @param stepValue  ID步长
     * @return 唯一id
     */
    @JvmStatic
    fun getNextIdString(key: String?, expireTime: Duration?, initValue: Long, stepValue: Long): String {
        return Convert.toStr(getNextId(key, expireTime, initValue, stepValue))
    }

    /**
     * 获取指定业务key的唯一id字符串 (ID初始值=1,ID步长=1)
     *
     * @param key        业务key
     * @param expireTime 过期时间
     * @return 唯一id
     */
    @JvmStatic
    fun getNextIdString(key: String?, expireTime: Duration?): String {
        return Convert.toStr(getNextId(key, expireTime))
    }

    /**
     * 获取指定业务key的唯一id字符串 (ID初始值=1,ID步长=1)，不足位数自动补零
     *
     * @param key        业务key
     * @param expireTime 过期时间
     * @param width      位数，不足左补0
     * @return 补零后的唯一id字符串
     */
    @JvmStatic
    fun getPaddedNextIdString(key: String?, expireTime: Duration?, width: Int): String? {
        return StringUtils.leftPad(getNextIdString(key, expireTime), width, '0')
    }

    /**
     * 获取 yyyyMMdd 格式的唯一id
     *
     * @return 唯一id
     */
    @JvmStatic
    @Deprecated("请使用 {@link #getDateId(String)} 或 {@link #getDateId(String, boolean)}、{@link #getDateId(String, boolean, int)}，确保不同业务的ID连续性")
    fun getDateId(): String? {
        return getDateId("")
    }

    /**
     * 获取 prefix + yyyyMMdd 格式的唯一id
     *
     * @param prefix 业务前缀
     * @return 唯一id
     */
    @JvmStatic
    fun getDateId(prefix: String?): String? {
        return getDateId(prefix, true)
    }

    /**
     * 获取 prefix + yyyyMMdd 格式的唯一id
     *
     * @param prefix       业务前缀
     * @param isWithPrefix id是否携带业务前缀
     * @return 唯一id
     */
    @JvmStatic
    fun getDateId(prefix: String?, isWithPrefix: Boolean): String? {
        return getDateId(prefix, isWithPrefix, -1)
    }

    /**
     * 获取 prefix + yyyyMMdd 格式的唯一id (启用ID补位，补位长度 = [.DEFAULT_MIN_ID_CAPACITY_BITS])}）
     *
     * @param prefix       业务前缀
     * @param isWithPrefix id是否携带业务前缀
     * @return 唯一id
     */
    @JvmStatic
    fun getPaddedDateId(prefix: String?, isWithPrefix: Boolean): String? {
        return getDateId(prefix, isWithPrefix, DEFAULT_MIN_ID_CAPACITY_BITS)
    }

    /**
     * 获取 prefix + yyyyMMdd 格式的唯一id
     *
     * @param prefix            业务前缀
     * @param isWithPrefix      id是否携带业务前缀
     * @param minIdCapacityBits 最小ID容量位数，小于该位数的ID，左补0（小于等于0表示不启用补位）
     * @return 唯一id
     */
    @JvmStatic
    fun getDateId(prefix: String?, isWithPrefix: Boolean, minIdCapacityBits: Int): String? {
        return getDateId(prefix, isWithPrefix, minIdCapacityBits, LocalDate.now())
    }

    /**
     * 获取 prefix + yyyyMMdd 格式的唯一id
     *
     * @param prefix            业务前缀
     * @param isWithPrefix      id是否携带业务前缀
     * @param minIdCapacityBits 最小ID容量位数，小于该位数的ID，左补0（小于等于0表示不启用补位）
     * @param time              时间
     * @return 唯一id
     */
    @JvmStatic
    fun getDateId(prefix: String?, isWithPrefix: Boolean, minIdCapacityBits: Int, time: LocalDate): String {
        return getDateId(prefix, isWithPrefix, minIdCapacityBits, time, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE)
    }

    /**
     * 获取 prefix + yyyyMMdd 格式的唯一id
     *
     * @param prefix            业务前缀
     * @param isWithPrefix      id是否携带业务前缀
     * @param minIdCapacityBits 最小ID容量位数，小于该位数的ID，左补0（小于等于0表示不启用补位）
     * @param time              时间
     * @param initValue         ID初始值
     * @param stepValue         ID步长
     * @return 唯一id
     */
    @JvmStatic
    fun getDateId(
        prefix: String?,
        isWithPrefix: Boolean,
        minIdCapacityBits: Int,
        time: LocalDate,
        initValue: Long,
        stepValue: Long
    ): String {
        return getDatePatternId(
            prefix,
            isWithPrefix,
            minIdCapacityBits,
            time,
            DatePattern.PURE_DATE_FORMATTER,
            DEFAULT_EXPIRE_TIME_DAY,
            initValue,
            stepValue
        )
    }

    /**
     * 获取 yyyyMMddHHmmss 格式的唯一id
     *
     * @return 唯一id
     */
    @JvmStatic
    @Deprecated("请使用 {@link #getDateTimeId(String)} 或 {@link #getDateTimeId(String, boolean)}、{@link #getDateTimeId(String, boolean, int)}，确保不同业务的ID连续性")
    fun getDateTimeId(): String? {
        return getDateTimeId("", false)
    }

    /**
     * 获取 prefix + yyyyMMddHHmmss 格式的唯一id
     *
     * @param prefix 业务前缀
     * @return 唯一id
     */
    @JvmStatic
    fun getDateTimeId(prefix: String?): String? {
        return getDateTimeId(prefix, true)
    }

    /**
     * 获取 prefix + yyyyMMddHHmmss 格式的唯一id
     *
     * @param prefix       业务前缀
     * @param isWithPrefix id是否携带业务前缀
     * @return 唯一id
     */
    @JvmStatic
    fun getDateTimeId(prefix: String?, isWithPrefix: Boolean): String? {
        return getDateTimeId(prefix, isWithPrefix, -1)
    }

    /**
     * 获取 prefix + yyyyMMddHHmmss 格式的唯一id (启用ID补位，补位长度 = [.DEFAULT_MIN_ID_CAPACITY_BITS])}）
     *
     * @param prefix       业务前缀
     * @param isWithPrefix id是否携带业务前缀
     * @return 唯一id
     */

    fun getPaddedDateTimeId(prefix: String?, isWithPrefix: Boolean): String? {
        return getDateTimeId(prefix, isWithPrefix, DEFAULT_MIN_ID_CAPACITY_BITS)
    }

    /**
     * 获取 prefix + yyyyMMddHHmmss 格式的唯一id
     *
     * @param prefix            业务前缀
     * @param isWithPrefix      id是否携带业务前缀
     * @param minIdCapacityBits 最小ID容量位数，小于该位数的ID，左补0（小于等于0表示不启用补位）
     * @return 唯一id
     */
    @JvmStatic
    fun getDateTimeId(prefix: String?, isWithPrefix: Boolean, minIdCapacityBits: Int): String? {
        return getDateTimeId(prefix, isWithPrefix, minIdCapacityBits, LocalDateTime.now())
    }

    /**
     * 获取 prefix + yyyyMMddHHmmss 格式的唯一id
     *
     * @param prefix            业务前缀
     * @param isWithPrefix      id是否携带业务前缀
     * @param minIdCapacityBits 最小ID容量位数，小于该位数的ID，左补0（小于等于0表示不启用补位）
     * @param time              时间
     * @return 唯一id
     */
    @JvmStatic
    fun getDateTimeId(prefix: String?, isWithPrefix: Boolean, minIdCapacityBits: Int, time: LocalDateTime): String? {
        return getDateTimeId(prefix, isWithPrefix, minIdCapacityBits, time, DEFAULT_INIT_VALUE, DEFAULT_STEP_VALUE)
    }

    /**
     * 获取 prefix + yyyyMMddHHmmss 格式的唯一id
     *
     * @param prefix            业务前缀
     * @param isWithPrefix      id是否携带业务前缀
     * @param minIdCapacityBits 最小ID容量位数，小于该位数的ID，左补0（小于等于0表示不启用补位）
     * @param initValue         ID初始值
     * @param stepValue         ID步长
     * @return 唯一id
     */
    @JvmStatic
    fun getDateTimeId(
        prefix: String?,
        isWithPrefix: Boolean,
        minIdCapacityBits: Int,
        time: LocalDateTime,
        initValue: Long,
        stepValue: Long
    ): String {
        return getDatePatternId(
            prefix,
            isWithPrefix,
            minIdCapacityBits,
            time,
            DatePattern.PURE_DATETIME_FORMATTER,
            DEFAULT_EXPIRE_TIME_MINUTE,
            initValue,
            stepValue
        )
    }

    /**
     * 获取指定业务key的指定时间格式的ID
     *
     * @param prefix            业务前缀
     * @param isWithPrefix      id是否携带业务前缀
     * @param minIdCapacityBits 最小ID容量位数，小于该位数的ID，左补0（小于等于0表示不启用补位）
     * @param temporalAccessor  时间访问器
     * @param timeFormatter     时间格式
     * @param expireTime        过期时间
     * @param initValue         ID初始值
     * @param stepValue         ID步长
     * @return 唯一id
     */
    private fun getDatePatternId(
        prefix: String?,
        isWithPrefix: Boolean,
        minIdCapacityBits: Int,
        temporalAccessor: TemporalAccessor,
        timeFormatter: DateTimeFormatter,
        expireTime: Duration?,
        initValue: Long,
        stepValue: Long
    ): String {
        // 时间前缀
        val timePrefix = timeFormatter.format(temporalAccessor)
        // 业务前缀 + 时间前缀 构建 prefixKey
        val prefixKey = "${StringUtils.blankToDefault(prefix, "")}$timePrefix"

        // 获取id，例 -> 1
        var nextId = getNextIdString(prefixKey, expireTime, initValue, stepValue)

        // minIdCapacityBits 大于0，且 nextId 的长度小于 minIdCapacityBits，则左补0
        if (minIdCapacityBits > 0 && nextId.length < minIdCapacityBits) {
            nextId = StringUtils.leftPad(nextId, minIdCapacityBits, '0')
        }

        // 是否携带业务前缀
        if (isWithPrefix) {
            // 例 -> P202507031
            // 其中 P 为业务前缀，202507031 为 yyyyMMdd 格式时间, 1 为nextId
            return "$prefixKey$nextId"
        }
        // 例 -> 202507031
        // 其中 202507031 为 yyyyMMdd 格式时间, 1 为nextId
        return "$timePrefix$nextId"
    }
}
